package com.fingard.dsp.bank.directbank.dbs01.util;

import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.*;
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.jcajce.*;
import org.bouncycastle.util.io.Streams;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Date;
import java.util.Iterator;

/**
 * Created by F on 2018/12/19.
 */
public class BouncyCastlePgpHelper {
    static {
        //Security.addProvider(new BouncyCastleProvider());
        //增加加密提供者
        Security.insertProviderAt(new BouncyCastleProvider(),1);
    }

    /**
     * 解除秘钥长度限制:
     * 1、通过反射移除了isRestricted 的变量修饰符：final
     * 2、然后将isRestricted 赋值为false
     */
    static {
        try {
            Class<?> clazz = Class.forName("javax.crypto.JceSecurity");
            Field nameField = clazz.getDeclaredField("isRestricted");

            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);

            nameField.setAccessible(true);
            nameField.set(null, java.lang.Boolean.FALSE);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public static PGPPublicKey readPublicKey(InputStream input) throws IOException, PGPException {

        PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(input), new JcaKeyFingerprintCalculator());
        PGPPublicKey key = pgpPub.getKeyRings().next().getPublicKeys().next();

        if (key.isEncryptionKey())
            return key;

        throw new IllegalArgumentException("Can't find encryption key in key ring.");
    }


    public static PGPSecretKey readSecretKey(InputStream in) throws Exception {

        PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(in), new JcaKeyFingerprintCalculator());
        PGPSecretKey key = pgpSec.getKeyRings().next().getSecretKeys().next();

        if (key.isSigningKey())
            return key;

        throw new IllegalArgumentException("Can't find signing key in key ring.");
    }

    public static PGPPrivateKey findPrivateKey(InputStream in, char[] pass) throws Exception {

        PGPSecretKey pgpSecKey = readSecretKey(in);

        return pgpSecKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(pass));
    }

    public static byte[] compressSigned(byte[] clearData, String fileName, PGPPublicKey publicKey, PGPSecretKey secretKey,
                                        PGPPrivateKey privateKey) throws Exception {

        ByteArrayOutputStream out = new ByteArrayOutputStream();

        PGPSignatureGenerator sGen =
                new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(secretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256).setProvider("BC"));

        sGen.init(PGPSignature.BINARY_DOCUMENT, privateKey);

        Iterator<String> it = secretKey.getPublicKey().getUserIDs();
        if (it.hasNext()) {
            PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();

            spGen.setSignerUserID(false, it.next());
            sGen.setHashedSubpackets(spGen.generate());
        }

        PGPCompressedDataGenerator cGen = new PGPCompressedDataGenerator(PGPCompressedData.ZIP);
        BCPGOutputStream bOut = new BCPGOutputStream(cGen.open(out));
        sGen.generateOnePassVersion(true).encode(bOut);

        PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
        OutputStream lOut = lData.open(bOut, PGPLiteralData.BINARY, "pgp", clearData.length, new Date());
        lOut.write(clearData);
        lData.close();

        sGen.update(clearData);
        sGen.generate().encode(bOut);

        cGen.close();

        return out.toByteArray();
    }

    public static String encryptSigned(String payload, PGPPublicKey publicKey, PGPSecretKey secretKey, PGPPrivateKey privateKey)
            throws Exception {

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ArmoredOutputStream armored = new ArmoredOutputStream(out);

        //byte[] compressed = compressSigned(payload.getBytes(), "pgp", publicKey, secretKey, privateKey);
        System.err.println("系统默认字符编码：" + Charset.defaultCharset());
        byte[] compressed = compressSigned(payload.getBytes("UTF-8"), "pgp", publicKey, secretKey, privateKey);

        PGPEncryptedDataGenerator cPk =
                new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(PGPEncryptedData.AES_256).setSecureRandom(new SecureRandom())
                        .setWithIntegrityPacket(true));
        cPk.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(publicKey));

        OutputStream cOut = cPk.open(armored, compressed.length);
        cOut.write(compressed);

        cOut.close();
        armored.close();

        return out.toString("utf-8");
    }

    public static String decryptAndVerify(String encrypted, PGPPrivateKey privateKey, PGPPublicKey key) throws Exception {

        JcaPGPObjectFactory objectFactory =
                new JcaPGPObjectFactory(PGPUtil.getDecoderStream(new ByteArrayInputStream(encrypted.getBytes("utf-8"))));

        Object obj = objectFactory.nextObject();
        PGPEncryptedDataList enc =
                obj instanceof PGPEncryptedDataList ? (PGPEncryptedDataList) obj : (PGPEncryptedDataList) objectFactory.nextObject();
        PGPPublicKeyEncryptedData pbe = (PGPPublicKeyEncryptedData) enc.getEncryptedDataObjects().next();

        InputStream clear = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("BC").build(privateKey));
        objectFactory = new JcaPGPObjectFactory(clear);
        Object message = objectFactory.nextObject();

        objectFactory = new JcaPGPObjectFactory(((PGPCompressedData) message).getDataStream());

        PGPOnePassSignature ops = ((PGPOnePassSignatureList) objectFactory.nextObject()).get(0);
        ops.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), key);

        InputStream input = ((PGPLiteralData) objectFactory.nextObject()).getInputStream();
        byte[] decrypted = Streams.readAll(input);
        ops.update(decrypted);

        if (!ops.verify(((PGPSignatureList) objectFactory.nextObject()).get(0)))
            throw new Exception("pgp verify sign error.");

        return new String(decrypted, "utf-8");
    }



    public static String decrypt(String encrypted, PGPPrivateKey privateKey) throws Exception {

        JcaPGPObjectFactory objectFactory =
                new JcaPGPObjectFactory(PGPUtil.getDecoderStream(new ByteArrayInputStream(encrypted.getBytes("utf-8"))));

        Object obj = objectFactory.nextObject();
        PGPEncryptedDataList enc =
                obj instanceof PGPEncryptedDataList ? (PGPEncryptedDataList) obj : (PGPEncryptedDataList) objectFactory.nextObject();
        PGPPublicKeyEncryptedData pbe = (PGPPublicKeyEncryptedData) enc.getEncryptedDataObjects().next();

        InputStream clear = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("BC").build(privateKey));
        objectFactory = new JcaPGPObjectFactory(clear);
        Object message = objectFactory.nextObject();

        objectFactory = new JcaPGPObjectFactory(((PGPCompressedData) message).getDataStream());

//	    PGPOnePassSignature ops = ((PGPOnePassSignatureList) objectFactory.nextObject()).get(0);
//	    ops.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), key);

        InputStream input = ((PGPLiteralData) objectFactory.nextObject()).getInputStream();
        byte[] decrypted = Streams.readAll(input);
//	    ops.update(decrypted);

//	    if (!ops.verify(((PGPSignatureList) objectFactory.nextObject()).get(0)))
//	      throw new Exception("pgp verify sign error.");

        return new String(decrypted, "utf-8");
    }
}
